package com.zzyl.nursing.service.impl;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.DeviceDataMapper;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import static com.zzyl.common.constant.CacheConstants.IOT_DEVICE_LAST_DATA;

/**
 * 报警规则Service业务层处理
 *
 * @author ruoyi
 * @date 2025-09-22
 */
@Service
@Slf4j
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DeviceDataMapper deviceDataMapper;
    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private IAlertDataService alertDataService;

    @Value("${alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;
    @Value("${alert.managerRole}")
    private String managerRole;

    /**
     * 查询报警规则
     *
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id) {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule) {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule) {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     *
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids) {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     *
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id) {
        return removeById(id) ? 1 : 0;
    }


    /**
     * 报警规则过滤
     */
    @Override
    public void alertFilter() {

        //1.查询启用状态的规则条数
        Long count = this.lambdaQuery().eq(AlertRule::getStatus, 1).count();

        //2.判断有没有启用中的规则，如果没有，直接结束定时任务
        if (count <= 0) {
            return;
        }

        //3.如果有，从redis中查询所有设备最近一次的上报数据
        List<Object> deviceLastValues = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);

        //4.判断所有设备的上报集合数据，是否为空
        if (ObjectUtil.isEmpty(deviceLastValues)) {
            return;
        }

        //5.如果不为空，将所有设备最后一次上报数据，添加到数据集合中（数据格式标准化处理）
        List<DeviceData> waitCheckDeviceDataList = new ArrayList<>();
        deviceLastValues.forEach(item -> {
            waitCheckDeviceDataList.addAll((List<DeviceData>) item);
        });
        if (ObjectUtil.isEmpty(waitCheckDeviceDataList)) {
            return;
        }

        //6.开始遍历每一条上报的属性数据，检测是否满足报警条件
        waitCheckDeviceDataList.forEach(deviceData -> alertCheck(deviceData));


    }

    /**
     * 报警检测
     *
     * @param deviceData
     */
    private void alertCheck(DeviceData deviceData) {
        //1.该属性上报时间超过1分钟不检测（大概率已经检测过或停止了）
        LocalDateTime alarmTime = deviceData.getAlarmTime();
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.MINUTES);
        //判断有么有超一分钟
        if (between > 1) {
            return;
        }

        //2.查询该设备所属产品下的通用规则
        List<AlertRule> commonRules = this.lambdaQuery()
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getStatus, 1)
                .eq(AlertRule::getIotId, "-1")
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .list();

        //3.查询设备特有规则
        List<AlertRule> deviceRules = this.lambdaQuery()
                .eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getStatus, 1)
                .eq(AlertRule::getIotId, deviceData.getIotId())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .list();
        //4.合并所有使用与当前设备的规则
        Collection<AlertRule> alertRules = CollUtil.addAll(commonRules, deviceRules);

        //5.判断规则集合是否为空，如果为空，直接结束，检测下一条数据
        if (CollUtil.isEmpty(alertRules)) {
            return;
        }

        //6.不为空，遍历每一条规则，对该条上报数据进行检测
        alertRules.forEach(alertRule -> deviceDateRuleCheck(alertRule, deviceData));

    }

    /**
     * 使用报警规则对上报数据进行检测
     *
     * @param alertRule  规则
     * @param deviceData 数据
     */
    private void deviceDateRuleCheck(AlertRule alertRule, DeviceData deviceData) {
        //1.判断该条数据是否在该条规则的生效时间内
        //1.1 分别获取设备上传时间和规则内的生效时间，开始-结束时间
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        String[] timeSplit = alertEffectivePeriod.split("~");
        LocalTime startTime = LocalTime.parse(timeSplit[0]);
        LocalTime endTime = LocalTime.parse(timeSplit[1]);
        LocalTime localTime = deviceData.getAlarmTime().toLocalTime();

        //1.2 判断时间是否在范围内，如果不在，直接结束，检测下一条规则
        if (!(localTime.isAfter(startTime) && localTime.isBefore(endTime))) {
            return;
        }

        //2.判断上报数据是否触发该条规则的阈值(上报数据值+操作符+阈值 == 60 <=65）
        //2.1组装报警数的redis key
        String alertCountKey = CacheConstants.IOT_COUNT_ALERT + deviceData.getIotId() + ":" + deviceData.getFunctionId() + ":" + alertRule.getIotId();

        // int i = Double.valueOf(deviceData.getDataValue()).compareTo(alertRule.getValue());
        int compare = NumberUtil.compare(Double.valueOf(deviceData.getDataValue()), alertRule.getValue());
        //>=和测试值大于阈值   或  <和测试值小于阈值 就判断对了
        if (">=".equals(alertRule.getOperator()) && (compare >= 0) || ("<".equals(alertRule.getOperator()) && compare < 0)) {
            //2.2继续检测下一条数据
            log.info("当前上报日志：{} 符合了规则：{}，可以继续处理...", deviceData, alertRule);
        } else {
            //2.3 不符合规则，删除redis中的报警次数
            redisTemplate.delete(alertCountKey);
            return;
        }

        //3.代码走到这，说明已经触发报警阈值，可以添加报警次数，但是在这之前，还要判断是否在（沉默周期）范围内
        //3.1组装沉默周期Key
        String silentKey = CacheConstants.IOT_SILENT_ALERT + deviceData.getIotId() + ":" + deviceData.getFunctionId() + ":" + alertRule.getIotId();
        //3.2 判断Key是否存在，如果存在，说明还在当前报警规则的沉默周期内，直接结束。
        Boolean exists = redisTemplate.hasKey(silentKey);
        if (exists) {
            return;
        }

        //4.不在沉默周期，报警次数加1，然后判断报警次数是否等于持续周期，如果等于，需要最终报警。
        Long count = redisTemplate.opsForValue().increment(alertCountKey);
        if (count < alertRule.getDuration()) {
            return;
        }

        //5.达到了持续周期，开始报警，既然马上报警，删除累计报警次数
        redisTemplate.delete(alertCountKey);

        //6.既然马上要触发最终报警，添加沉默周期，后续上报异常数据，如果在沉默周期内，停止累加报警次数
        redisTemplate.opsForValue().set(silentKey, 1, Duration.ofMinutes(alertRule.getAlertSilentPeriod()));

        //7.找到需要报警通知的人，开始保存报警数据（相当于报警）
        List<Long> userIds = new ArrayList<>();
        if (alertRule.getAlertDataType() == 0) {
            //7.1 判断报警数据类型，0：老人异常数据，1：设备异常数据 0通知护理人， 1通知设备维修工
            if (deviceData.getLocationType() == 0) {
                //7.1.1如果是随身设备
                userIds = deviceMapper.selectNursingIdByIotIdWithElder(deviceData.getIotId());
            } else if (deviceData.getLocationType() == 1 && deviceData.getPhysicalLocationType() == 2) {
                //7.1.3 如果是固定设备
                userIds = deviceMapper.selectNursingIdByIotIdWithBed(deviceData.getIotId());
            }

        } else {
            //7.2通知设备维修工
            userIds = sysUserRoleMapper.selectByRoleName(deviceMaintainerRole);

        }
        //8.通知管理员
        List<Long> longs = sysUserRoleMapper.selectByRoleName(managerRole);
        //9.人员去重
        ArrayList<Long> needNotifyUserIds = CollUtil.distinct(CollUtil.addAll(userIds, longs));

        //10保存报警数据到AlertData
        batchSaveAlertData(alertRule, deviceData, needNotifyUserIds);

    }

    /**
     * 批量保存报警数据
     *
     * @param alertRule
     * @param deviceData
     * @param needNotifyUserIds
     */
    private void batchSaveAlertData(AlertRule alertRule, DeviceData deviceData, ArrayList<Long> needNotifyUserIds) {
        log.error("开始通知养老工作人员：{}", needNotifyUserIds);
        //1.进行属性拷贝（DeviceData -> AlertData）
        AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
        alertData.setId(null);

        //2.报警规则属性补全
        alertData.setAlertRuleId(alertRule.getId());
        // (功能名称）（运算符）（阈值）（持续周期）（沉默周期）-》 温度》=40，持续5个周期，沉默5分钟
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(alertRule.getFunctionName()).append(alertRule.getOperator()).append(alertRule.getValue())
                .append("持续").append(alertRule.getDuration()).append("个周期，沉默")
                .append(alertRule.getAlertSilentPeriod()).append("分钟");
        alertData.setAlertReason(stringBuilder.toString());
        alertData.setType(alertRule.getAlertDataType());
        alertData.setStatus(0);

        //3.循环待通知人ID集合，组装AlertData集合
        List<AlertData> collect = needNotifyUserIds.stream()
                .map(userId -> BeanUtil.toBean(alertData, AlertData.class).setUserId(userId))
                .collect(Collectors.toList());


        //4.批量保存
        this.alertDataService.saveBatch(collect);

    }
}
